package cn.kivensoft.util;

import java.util.concurrent.locks.StampedLock;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;

/** 乐观读写锁扩展子类, 扩展了三个常见场景的读写函数, 适用于读多写少的情况
 * @author kiven lee
 * @version 1.0
 * date: 2019-11-20
 */
final public class OptimisticLock extends StampedLock {
	private static final long serialVersionUID = 1L;

	/** 乐观读(非阻塞读), 先尝试乐观读, 校验失败后, 转为悲观读(阻塞读)
	 * @param func 读取变量的回调函数
	 * @return 读取的结果
	 */
	public <R> R optimisticForRead(Supplier<R> func) {
		long stamp = tryOptimisticRead(); // 非阻塞获取版本信息
		R ret = func.get(); // 拷贝变量到线程本地堆栈
		if (!validate(stamp)) // 校验失败
			ret = lockForRead(func);
		return ret;
	}

	/** 悲观读
	 * @param func 读取变量的回调函数
	 * @return 读取的结果
	 */
	public <R> R lockForRead(Supplier<R> func) {
		long stamp = readLock();
		try {
			return func.get();
		} finally {
			unlock(stamp);
		}
	}

	/** 悲观写
	 * @param func 写入回调函数，返回值由用户自行定义
	 * @return func的返回值
	 */
	public <R> R lockForWrite(Supplier<R> func) {
		// 涉及对共享资源的修改，使用写锁-独占操作
		long stamp = writeLock();
		try {
			return func.get();
		} finally {
			unlockWrite(stamp);
		}
	}

	/**
	 * 条件写入，优化写入代码使其尽可能快速的写入
	 * <li>1. 先乐观读取，判断pred是否返回true，是的话继续</li>
	 * <li>2. 校验乐观锁，如果校验成功，直接继续，校验失败，则改为读锁，再次判断pred是否返回true，是的话继续</li>
	 * <li>3. 尝试升级为写锁，如果升级失败，先释放读锁，然后获取写锁</li>
	 * <li>4. 调用func写入数据</li>
	 * <li>5. 释放锁</li>
	 *
	 * @param pred 测试回调函数，返回true将升级为写锁
	 * @param func 回调函数
	 */
	public void lockForWrite(BooleanSupplier pred, Runnable func) {
		long stamp = tryOptimisticRead(); // 获取乐观读锁
		try {
			while (pred.getAsBoolean()) { // 检查条件
				if (!validate(stamp)) { // 验证版本号
					stamp = readLock(); // 如果验证失败，获取悲观读锁
				}
				long writeStamp = tryConvertToWriteLock(stamp); // 尝试升级为写锁
				if (writeStamp != 0L) {
					stamp = writeStamp; // 升级成功
					func.run();
					break;
				} else {
					unlockRead(stamp); // 释放悲观读锁
					stamp = writeLock(); // 直接获取写锁
				}
			}
		} finally {
			unlock(stamp); // 释放锁
		}
	}

}
